package com.hui.platform.falseworkapi.common.security;

import com.hui.platform.falseworkapi.common.security.annotation.SecurityIgnored;
import com.hui.platform.falseworkapi.common.security.constant.JwtProperties;
import com.hui.platform.falseworkapi.common.security.jwt.*;
import com.hui.platform.falseworkapi.common.security.utils.JwtTokenUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.util.Map;
import java.util.Set;

/**
 * Security第二条过滤链：jwt相关配置
 * @author hui
 */
@Order(2)
@Configuration
@EnableWebSecurity
@ConditionalOnWebApplication
public class SecurityJwtConfig extends WebSecurityConfigurerAdapter {

    private final MessageSource messageSource;

    private final UserDetailsService userDetailsService;

    private final JwtTokenUtil jwtTokenUtil;

    private final JwtProperties jwtProperties;

    @Autowired
    public SecurityJwtConfig(MessageSource messageSource,
                             UserDetailsService userDetailsService,
                             JwtTokenUtil jwtTokenUtil,
                             JwtProperties jwtProperties) {
        this.messageSource = messageSource;
        this.userDetailsService = userDetailsService;
        this.jwtTokenUtil = jwtTokenUtil;
        this.jwtProperties = jwtProperties;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //关闭csrf
        http.csrf().disable();

        //基于token，所以不需要session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        //添加JWT认证过滤器filter
        http.addFilterBefore(new JwtAuthorizationTokenFilter(userDetailsService, jwtTokenUtil, jwtProperties, messageSource),
                UsernamePasswordAuthenticationFilter.class);

        //添加自定义未授权和未登录结果返回（异常处理）
        http.exceptionHandling()
                .accessDeniedHandler(new JsonAccessDeniedHandler(messageSource))
                .authenticationEntryPoint(new JsonAuthenticationEntryPoint(messageSource));

        //SecurityIgnored注解忽略路径
        securityIgnoredConfigurer(http);

        //相关路径配置，所有请求需要验证身份
        http.authorizeRequests().anyRequest().authenticated();
    }

    @Autowired
    private UserDetailsProvider userDetailsProvider;

    /**
     * 用户细节验证
     * @return 用户细节服务
     */
    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        return username -> {
            if (userDetailsProvider.supported(username)) {
                return userDetailsProvider.loadUserByUsername(username);
            }
            throw new UsernameNotFoundException("not found : " + username);
        };
    }

    /**
     * 配置忽略的API
     *
     * @param http HttpSecurity
     * @throws Exception 异常
     */
    private void securityIgnoredConfigurer(HttpSecurity http) throws Exception {
        //设置注解忽略的路径
        //获取HandleMapping方法
        RequestMappingHandlerMapping mapping = getApplicationContext().getBean(RequestMappingHandlerMapping.class);
        // 获取url与类和方法的对应信息
        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
        //获取匹配的信息
        for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : map.entrySet()) {
            RequestMappingInfo mappingInfo = entry.getKey();
            HandlerMethod method = entry.getValue();
            SecurityIgnored securityIgnored = null;

            //声明类（父类）
            if (method.getMethod().getDeclaringClass().isAnnotationPresent(SecurityIgnored.class)) {
                securityIgnored = method.getMethod().getDeclaringClass().getAnnotation(SecurityIgnored.class);
            }
            //所在类（当前类）
            if (method.getBeanType().isAnnotationPresent(SecurityIgnored.class)) {
                securityIgnored = method.getBeanType().getAnnotation(SecurityIgnored.class);
            }
            //方法上
            if (method.hasMethodAnnotation(SecurityIgnored.class)) {
                securityIgnored = method.getMethodAnnotation(SecurityIgnored.class);
            }

            //判断是否存在忽略注解以及是否忽略
            if (securityIgnored != null && securityIgnored.value()) {
                //匹配路径 Spring Security 会匹配URI参数
                Set<String> patterns = mappingInfo.getPatternsCondition().getPatterns();
                //过滤匹配规则
                //修复bug：RequestMapping注解忽略的问题（忽略全部method方法）
                Set<RequestMethod> httpMethods = mappingInfo.getMethodsCondition().getMethods();
                if (httpMethods == null || httpMethods.isEmpty()) {
                    http.authorizeRequests().antMatchers(patterns.toArray(new String[]{})).permitAll();
                    continue;
                }
                for (RequestMethod t : httpMethods) {
                    http.authorizeRequests().antMatchers(HttpMethod.valueOf(t.name()), patterns.toArray(new String[]{})).permitAll();
                }
            }
        }

    }
}
